/*
 * SyncResource.java
 *
 * Created on April 12, 2007, 1:39 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.atomojo.app.admin;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.atomojo.app.App;
import org.atomojo.app.Storage;
import org.atomojo.app.WebComponent;
import org.atomojo.app.client.XMLRepresentationParser;
import org.atomojo.app.db.DB;
import org.infoset.xml.Document;
import org.infoset.xml.Element;
import org.infoset.xml.XMLException;
import org.infoset.xml.util.DocumentDestination;
import org.restlet.data.Disposition;
import org.restlet.data.MediaType;
import org.restlet.data.Reference;
import org.restlet.data.Status;
import org.restlet.representation.FileRepresentation;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;
import org.restlet.resource.ServerResource;

/**
 *
 * @author alex
 */
public class BackupResource extends ServerResource implements AdminXML
{
   
   /** Creates a new instance of SyncResource */
   public BackupResource() {
      setNegotiated(false);
   }
   
   public Representation get()
   {
      final DB db = (DB)getRequest().getAttributes().get(App.DB_ATTR);
      final Storage storage = (Storage)getRequest().getAttributes().get(App.STORAGE_ATTR);
      final Reference resourceBase = (Reference)getRequest().getAttributes().get(App.RESOURCE_BASE_ATTR);
      File tmpDir = (File)getContext().getAttributes().get(WebComponent.ATOMOJO_TMP_DIR);
      long tstamp = System.currentTimeMillis();
      String baseName = db.getName()+".backup."+tstamp;
      /*
      File dir = new File(tmpDir,baseName);
      if (!dir.mkdirs()) {
         getResponse().setStatus(Status.SERVER_ERROR_INTERNAL,"Cannot create output directory.");
         return;
      }*/

      try {
         Backup backup = new Backup(db,storage,resourceBase);
         final File zipFile = new File(tmpDir,baseName+".zip");
         backup.toZip(baseName, zipFile);
         /*
         backup.toDirectory(dir);
         zipDir(dir,zipFile);
         deleteDir(dir);
          */
         getResponse().setStatus(Status.SUCCESS_OK);
         Representation rep = new FileRepresentation(zipFile,MediaType.APPLICATION_ZIP) {
            public void release() {
               zipFile.delete();
            }
         };
         Disposition disposition = new Disposition();
         disposition.setFilename("backup.zip");
         rep.setDisposition(disposition);
         return rep;
      } catch (Exception ex) {
         getContext().getLogger().log(Level.SEVERE,"Cannot perform backup to due to exception: "+ex.getMessage(),ex);
         getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
         return new StringRepresentation("Cannot perform backup to due to exception.");
         //deleteDir(dir);
      }
   }
   
   public void zipDir(File dir,File zipFile)
      throws java.io.IOException
   {
      Logger log = getLogger();
      ZipOutputStream zipStream = new ZipOutputStream(new FileOutputStream(zipFile));
      zipStream.setMethod(ZipOutputStream.DEFLATED);
      int baseLength = dir.getParentFile().getAbsolutePath().length();
      final List<File> queue = new ArrayList<File>();
      queue.add(dir);
      byte [] buffer = new byte[32768];
      while (queue.size()>0) {
         File target = queue.remove(queue.size()-1);
         if (target.isDirectory()) {
            target.listFiles(new FileFilter() {
               public boolean accept(File f)
               {
                  queue.add(f);
                  return false;
               }
            });
         } else {
            FileInputStream is = new FileInputStream(target);
            BufferedInputStream source = new BufferedInputStream(is);
 
            ZipEntry theEntry = new ZipEntry(target.getAbsolutePath().substring(baseLength+1));
            log.info("\tAdding: " + theEntry.getName());
 
            zipStream.putNextEntry(theEntry);
 
            int len;
            while ((len=source.read(buffer))>=0) {
               zipStream.write(buffer, 0, len);
            }
            zipStream.flush();
            zipStream.closeEntry();
            source.close();
         }
      }
      zipStream.close();
      
   }
   
   public void deleteDir(File dir)
   {
      final List<File> queue = new ArrayList<File>();
      queue.add(dir);
      boolean ok = true;
      int mark = -1;
      while (ok && queue.size()>0) {
         File target = queue.remove(queue.size()-1);
         if (target.isDirectory()) {
            if (mark==queue.size()) {
               ok = target.delete();
               mark = -1;
            } else {
               mark = queue.size();
               queue.add(target);
               target.listFiles(new FileFilter() {
                  public boolean accept(File f)
                  {
                     queue.add(f);
                     return false;
                  }
               });
            }
         } else {
            ok = target.delete();
         }
      }
      
   }
   
   public Representation post(Representation entity)
   {
      final DB db = (DB)getRequest().getAttributes().get(App.DB_ATTR);
      final Storage storage = (Storage)getRequest().getAttributes().get(App.STORAGE_ATTR);
      final Reference resourceBase = (Reference)getRequest().getAttributes().get(App.RESOURCE_BASE_ATTR);
      if (getRequest().getResourceRef().getRemainingPart().endsWith(".zip")) {
         getResponse().setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
         return null;
      }
      if (!XMLRepresentationParser.isXML(entity.getMediaType())) {
         getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
         return new StringRepresentation("Non-XML media type for entity body: "+entity.getMediaType().getName());
      }
      
      XMLRepresentationParser parser = new XMLRepresentationParser();
      
      try {
         DocumentDestination dest = new DocumentDestination();
         
         parser.parse(entity,AdminApplication.createAdminDocumentDestination(dest,AdminXML.NM_BACKUP));
         Document doc = dest.getDocument();
         
         Element top = doc.getDocumentElement();
         String location = top.getAttributeValue("location");
         if (location==null) {
            getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
            return new StringRepresentation("The 'location' attribute is missing.");
         }
         location = location.trim();
         if (location.length()==0) {
            getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
            return new StringRepresentation("The 'location' attribute is empty.");
         }
         File dir = new File(location);
         if (!dir.exists()) {
            if (!dir.mkdirs()) {
               getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
               return new StringRepresentation("The "+dir.getAbsolutePath()+" doesn't exist and can't be created.");
            }
         }
         if (!dir.canWrite()) {
            getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
            return new StringRepresentation("Cannot write to "+dir.getAbsolutePath());
         }

         try {
            Backup backup = new Backup(db,storage,resourceBase);
            backup.toDirectory(dir);
            getResponse().setStatus(Status.SUCCESS_CREATED);
            return null;
         } catch (Exception ex) {
            getContext().getLogger().log(Level.SEVERE,"Cannot perform backup to due to exception: "+ex.getMessage(),ex);
            getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);
            return new StringRepresentation("Cannot perform backup to due to exception: "+ex.getMessage());
         }
         
      } catch (IOException ex) {
         getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
         return new StringRepresentation("I/O exception: "+ex.getMessage());
      } catch (XMLException ex) {
         getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
         return new StringRepresentation("XML exception: "+ex.getMessage());
      }
   }
   
}
